# A generic stack class
# Ben Terris
# 11/20/2007

class Stack:
        """A generic stack class. Objects are pushed onto the stack and can be popped off two at a time."""

        def __init__(self):
                """Initialize a Stack. The stack grows upwards with the newest elements placed at the bottom."""
                # Initialize the stack as an empty Python list.
                self._elements = []
                self._elementCount = 0
                return
                
        def getNumElements(self):
                """Get the current number of elements contained in the stack."""
                return self._elementCount
        
        def getElement(self, index):
                """Get the current element at a given index in the stack."""
                try:
                        return self._elements[index]
                except:
                        return None
                
        
        def push(self, operand):
                """Push an operand onto the stack."""
                # append the operand to the end of the list
                
                self._elements.append(operand)
                self._elementCount = len(self._elements)
                
        
        def pop2(self):
                """Pop 2 objects off the stack."""
                # create and return a new list containing the 2 popped operands
                self._elementCount = len(self._elements) - 2
                return [ self._elements.pop(), self._elements.pop() ]


        def pop(self):
                """Pop a single object off the stack.""" 
                if self._elementCount>0:
                        self._elementCount = len(self._elements) - 1
                        return self._elements.pop()
                else:
                        return None
                 
        def peek(self, item=0):
                """Peek at the operand at the bottom of the stack without removing it."""
                if self._elementCount > item:
                        return self._elements[item-1]
                else:
                        return None

                        
        def exchange(self):
                """Exchange the bottom 2 operands"""
                ls = self._elements     
                ls[-2], ls[-1] = ls[-1], ls[-2]
                
                        
        def rollUp(self):
                """Roll the stack up, moving the top operand to the bottom."""  
                temp = self._elements[0]
                self._elements = self._elements[1:]
                self._elements.append(temp)
        
        def rollDown(self):
                """Roll the stack down, moving the bottom operand to the top."""        
                temp = self._elements[-1]
                self._elements = self._elements[:-1]
                self._elements.insert(0, temp)
        
        def pushUp(self):
                """Move the stack up, leaving a copy of the bottom operand in place.""" 
                self.push(self.peek())

if __name__ == '__main__':
        # run a battery of unit tests to ensure correct functionality
        x = Stack()
        
        def unittests():
                '''
        >>> x.push(1)
        >>> x.push(2)
        >>> x.push(3)
        >>> x.push("test")
        >>> x.push("element")
        >>> print "top element:", x.peek()
        top element: element
        
        >>> print "stack count:", x.getNumElements()
        stack count: 5
        
        Verify top 2 elements
        >>> print x._elements[-2]
        test
        >>> print x._elements[-1]
        element
        
        
        >>> x.exchange()
        
        Show that exchange worked
        >>> print x._elements[-2]
        element
        >>> print x._elements[-1]
        test
        
        
        Pop the correct elements and update stack count
        >>> x.pop2()
        ['test', 'element']
        >>> x.getNumElements()
        3
        
        Roll up the stack
        >>> x.rollUp()
        >>> x._elements[0]
        2
        >>> x._elements[1]
        3
        >>> x._elements[2]
        1
        
        Roll down the stack
        >>> x.rollDown()
        >>> x._elements[0]
        1
        >>> x._elements[1]
        2
        >>> x._elements[2]
        3
        
        Push up the stack
        >>> x.pushUp()
        >>> x._elements[0]
        1
        >>> x._elements[1]
        2
        >>> x._elements[2]
        3
        >>> x._elements[3]
        3
        
        '''



        from doctest import testmod
        testmod(verbose=True)
